# -*- coding: utf-8 -*-
import json
import logging
import time

from alipay import AliPay
from alipay.exceptions import AliPayException

from common.mch import db as mch_db
from common.withdraw.model import *
from common.withdraw import alipay_exception_handle
from common.withdraw import db as trans_db
from common.withdraw import handler as trans_handler
from common.cache import redis_cache
from common.timer import handler as timer_handler
from common.utils import tz
from common.utils.weight_sample import weight_sample

_LOGGER = logging.getLogger(__name__)


def _choose_chn(available_channels):
    # choose one
    candidates = []
    for chn in available_channels:
        # TODO, strategy later
        candidates.append(chn)
    if not candidates:
        return None
    # add weight
    c_dict = dict([(i, c.weight) for i, c in enumerate(candidates)])
    chosed_index = weight_sample(c_dict.keys(), c_dict, 1).next()
    return candidates[chosed_index]


def construnct_payer(order_id, chn, first_trans):
    if (first_trans and trans_db.fill_chn(order_id, chn.id)) or not first_trans:
        try:
            pay_handler = AliPay(
                appid=chn.app_id,
                app_notify_url='',
                app_private_key_path='',
                alipay_public_key_path='',
                app_private_key=chn.private_key,
                app_alipay_public_key=chn.public_key,
                sign_type='RSA2'
            )
        except Exception as e:
            return None
        except AliPayException as e:
            return None
        return {
            'no': chn.app_id,
            'handler': pay_handler,
            'payer': chn.payer_name,
            'chn_id': chn.id
        }
    else:
        return None


def trans_to_account(chosed_payer, order_id, amount, alipay_no, payee_real_name, remark=''):
    _LOGGER.info(u'Auto trans alipay, ready to trans %s %s %s', order_id, alipay_no, amount)
    _LOGGER.info(u'payer {} choosed, amount {}'.format(chosed_payer['payer'], amount))
    return chosed_payer['handler'].api_alipay_fund_trans_toaccount_transfer(out_biz_no=order_id,
                                                                            payee_type='ALIPAY_LOGONID',
                                                                            payee_account=alipay_no,
                                                                            payee_real_name=payee_real_name,
                                                                            amount=amount,
                                                                            payer_real_name=chosed_payer['payer'],
                                                                            remark=remark,
                                                                            ext_param=json.dumps({"order_title": u"商城"},
                                                                                                 ensure_ascii=False))



def get_payer(order):
    mch_id = order.mch_id
    if not order.channel_id:
        service_conf = mch_db.get_mch_chn(mch_id, 'withdraw_alipay')
        if not service_conf:
            _LOGGER.warn('mch %s has no binded withdraw chn', mch_id)
            return None, None
        channel_list = service_conf.chns.split(',')
        available_channels = trans_db.get_channels_in_ids(channel_list)
        if not available_channels:
            _LOGGER.warn('mch %s has no available withdraw chn', mch_id)
            return None, None
        chn = _choose_chn(available_channels)
        if not chn:
            _LOGGER.warn('mch %s has no available withdraw chn by filter', mch_id)
            return None, None
        first_trans = True
    else:
        chn = trans_db.get_channel(order.channel_id)
        if not chn:
            _LOGGER.warn('order %s chosed chn %s not dissapeared!', order.id)
            return None, None
        first_trans = False
    _LOGGER.info('ready to trans order %s-%s by chn %s',
                 mch_id, order.id, chn.id)
    return construnct_payer(order.id, chn, first_trans), chn


def start():
    orders = trans_db.get_ready_orders()
    for order in orders:
        mch_id = order.mch_id

        chosed_payer, chn = get_payer(order)
        trans_info = {}
        amount = float(order.total_fee)
        updated_info = json.loads(order.extend or '{}')
        if not chosed_payer:
            _LOGGER.info('construnct_payer fail. %s', order.id)
            sts = WITHDRAW_STATUS.FAIL
            trans_info.update({
                'amount': amount,
                'code': 'NOT_VAILD_CHANNEL',
                'sub_code': 'NOT_VAILD_CHANNEL',
                'sub_msg': '没有可用通道',
                'out_biz_no': order.id,
                'pay_date': tz.local_now().strftime('%Y-%m-%d %H:%M:%S'),
            })
            updated_info.update({'auto_trans_info': trans_info})
            trans_db.update_withdraw(order.id, sts, updated_info)
            # notify
            success, withdraw_order = trans_handler.notify_mch(order.id)
            if not success:
                # start timer to notify again
                timer_handler.submit_notify_withdraw_event(withdraw_order)
            continue

        try:
            res = trans_to_account(chosed_payer, order.id, amount,
                                   order.payee_no, order.payee_real_name)
            # succ
            sts = WITHDRAW_STATUS.SUCC
            trans_db.update_channel_balance(chosed_payer['chn_id'], amount)
            trans_info.update({
                'payer_no': chosed_payer['no'],
                'code': res.get('code'),
                'amount': amount,
                'order_id': res.get('order_id'),
                'out_biz_no': res.get('out_biz_no'),
                'pay_date': res.get('pay_date'),
            })
            _LOGGER.info(u'Auto trans alipay, trans succ %s-%s %s %s %s', mch_id,
                         order.id, res.get('order_id'), order.payee_no, amount)
        except AliPayException as e:
            alipay_exception_handle.handle(e.get_sub_code(), mch_id, chosed_payer, order.id)
            sts = WITHDRAW_STATUS.FAIL
            trans_info.update({
                'amount': amount,
                'code': e.get_code(),
                'sub_code': e.get_sub_code(),
                'sub_msg': e.get_msg(),
                'out_biz_no': order.id,
                'pay_date': tz.local_now().strftime('%Y-%m-%d %H:%M:%S'),
            })
            _LOGGER.info(u'AlipayException, {}-{} {} {} {}'.format(
                mch_id, order.id, e.get_code(), e.get_sub_code(), e.get_msg()))
        except Exception as e:
            sts = WITHDRAW_STATUS.READY
            trans_info.update({
                'amount': amount,
                'code': '-1',
                'sub_code': '-1',
                'sub_msg': u'unexpected exception:%s' % e,
                'pay_date': tz.local_now().strftime('%Y-%m-%d %H:%M:%S'),
            })
            _LOGGER.info(u'Unexpected Exception, %s, %s', order.id, e)

        updated_info.update({'auto_trans_info': trans_info})
        trans_db.update_withdraw(order.id, sts, updated_info,
                                 trade_no=trans_info.get('order_id'))
        if order.status == WITHDRAW_STATUS.SUCC:
            redis_cache.set_withdraw_status(chosed_payer['no'], 0)
        # notify
        success, withdraw_order = trans_handler.notify_mch(order.id)
        if not success:
            # start timer to notify again
            timer_handler.submit_notify_withdraw_event(withdraw_order)


def construnct_single_payer(chn):
    pay_handler = AliPay(
        appid=chn.app_id,
        app_notify_url='',
        app_private_key_path='',
        alipay_public_key_path='',
        app_private_key=chn.private_key,
        app_alipay_public_key=chn.public_key,
        sign_type='RSA2'
    )
    return {
        'no': chn.app_id,
        'handler': pay_handler,
        'payer': chn.payer_name
    }


def single_withdraw(id, chn_id):
    trans_info = {}
    order = trans_db.get_order(id)
    mch_id = order.mch_id
    amount = float(order.total_fee)
    chn = trans_db.get_channel(chn_id)
    updated_info = json.loads(order.extend or '{}')
    try:
        chosed_payer = construnct_single_payer(chn)
        res = trans_to_account(chosed_payer, order.id, amount,
                               order.payee_no, order.payee_real_name)
    except AliPayException as e:
        print e.get_code()
        print e.get_sub_code()
        print e.get_msg()
        return
    except Exception as e:
        print (u'Unexpected Exception, %s, %s', order.id, e)
        return
    # succ
    sts = WITHDRAW_STATUS.SUCC
    trans_info.update({
        'payer_no': chosed_payer['no'],
        'code': res.get('code'),
        'amount': amount,
        'order_id': res.get('order_id'),
        'out_biz_no': res.get('out_biz_no'),
        'pay_date': res.get('pay_date'),
    })
    _LOGGER.info(u'Auto trans alipay, trans succ %s-%s %s %s %s', mch_id,
                 order.id, res.get('order_id'), order.payee_no, amount)
    updated_info.update({'auto_trans_info': trans_info})
    trans_db.update_withdraw(order.id, sts, updated_info,
                             trade_no=trans_info.get('order_id'))
    if order.status == WITHDRAW_STATUS.SUCC:
        redis_cache.set_withdraw_status(chosed_payer['no'], 0)
    # notify
    success, withdraw_order = trans_handler.notify_mch(order.id)
    if not success:
        # start timer to notify again
        timer_handler.submit_notify_withdraw_event(withdraw_order)


# 向固定支付宝 每次转一毛钱
def test_withdraw_channel(chn_id):
    _LOGGER.info(u'test_withdraw_channel in chn_id: %s', chn_id)
    try:
        chn = trans_db.get_channel(chn_id)
        chosed_payer = construnct_single_payer(chn)
        oid = int(time.time())
        res = trans_to_account(chosed_payer, oid, 0.1, 'oyozk14@163.com', u'王加彬')
        _LOGGER.info(u'test_withdraw_channel rsp : %s', res)
        rsp = str(res)
    except AliPayException as e:
        rsp = str(e.get_code()) + ' ' + e.get_msg()
        _LOGGER.info(u'test_withdraw_channel AlipayException,  {} {} {}'.format(
            e.get_code(), e.get_sub_code(), e.get_msg()))
    except Exception as e:
        _LOGGER.exception(u'test_withdraw_channel Exception e: %s', e)
        rsp = str(e)
    return rsp.encode('utf8')
